#include <stdio.h>
#include "csapp.h"
#include <stdbool.h>

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE  1049000
#define MAX_OBJECT_SIZE 102400
#define MAX_BLOCK_NUM   10
/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";

void handle_request(int fd);
void print_client_error(int fd, char* cause, char* errnum,
                  char* shortmsg, char* longmsg);
void prase_uri(char* uri, char* hostname, char* port, char* filename);
int  handle_request_header(rio_t* rio, char* client_request, 
                           char* host, char* filename);
void* thread(void* vargp);
void  init_rwlock();
void  init_cache();
char* read_cache(char* uri);
void  write_cache(char* uri, char* obj);
int   cal_cache_maxnum();

//读写锁结构体
typedef struct 
{
    sem_t   mutex;      //互斥信号量
    sem_t   w;          //写信号量
    int     readcnt;    //读者数量
} rwlock_t;

//缓存块结构体
typedef struct
{
    char    uri[MAXLINE];   //URI
    char    obj[MAX_OBJECT_SIZE];   //对象
    int     is_used;    //最近是否被使用
} cache_block_t;

cache_block_t cache[MAX_BLOCK_NUM];
rwlock_t rwlock;

int main(int argc, char **argv)
{
    int listenfd;
    int *connfd;
    listenfd = Open_listenfd(argv[1]);

    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    char client_hostname[MAXLINE], client_port[MAXLINE];
    pthread_t tid;

    //初始化读写锁和缓存
    init_rwlock();
    init_cache();

    //检查命令行参数
    if(2 != argc)
    {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(1);
    }

    while(1)
    {
        clientlen = sizeof(struct sockaddr_storage);
        //使用动态内存分配,防止恶意竞争
        connfd = (int* )Malloc(sizeof(int));
        *connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        //获取客户端的主机名和端口号
        Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);
        printf("Connected to (%s, %s)\n", client_hostname, client_port);
        //创建线程
        Pthread_create(&tid, NULL, thread, connfd);
    }
    return 0;
}

//初始化读写锁
void init_rwlock()
{
    sem_init(&rwlock.mutex, 0, 1);
    sem_init(&rwlock.w, 0, 1);
    rwlock.readcnt = 0;
}

//线程函数
void* thread(void* vargp)
{
    int connfd = *((int*) vargp);
    Pthread_detach(Pthread_self());
    Free(vargp);
    handle_request(connfd);
    Close(connfd);
    return NULL;
}

//初始化缓存
void init_cache()
{
    for(int i = 0; i < MAX_BLOCK_NUM; i++)
    {
        cache[i].is_used = 0;
        memset(cache[i].uri, 0, MAXLINE);
        memset(cache[i].obj, 0, MAX_OBJECT_SIZE);
    }
}

//计算缓存中最近的使用次数
int cal_cache_maxnum()
{
    int i = 0;
    int max = 0;
    for(; i < MAX_BLOCK_NUM; i++)
    {
        if(cache[i].is_used > max)
        {
            max = cache[i].is_used;
        }
    }
    return max;
}

//读缓存,这里是读者优先策略
char* read_cache(char* uri)
{
    //读者等待互斥锁
    P(&rwlock.mutex);
    if(1 == rwlock. readcnt)
    {
        //写者不应该在读者读取的时候写
        P(&rwlock. w);
    }
    rwlock.readcnt ++;
    //释放互斥锁
    V(&rwlock.mutex);

    char* obj = NULL;
    for(int i = 0; i < MAX_BLOCK_NUM; i++)
    {
        //查找缓存
        if(0 == strcmp(uri, cache[i].uri))
        {
            obj = (char*)Malloc(strlen(cache[i].obj) + 1);
            strcpy(obj, cache[i].obj);
            cache[i].is_used = cal_cache_maxnum() + 1;
            break;
        }
    }

    //读者等待互斥锁
    P(&rwlock.mutex);
    rwlock.readcnt--;
    if(0 == rwlock.readcnt)
    {
        //当读者数量为0时,释放写信号量
        V(&rwlock.w);
    }
    //释放互斥锁
    V(&rwlock.mutex);

    return obj;
}

//写缓存
void write_cache(char* uri, char* obj)
{
    //写者等待互斥锁
    P(&rwlock.w);
    int i = 0;
    //查找空的缓存块
    for(; i < MAX_BLOCK_NUM; i++)
    {
        if(0 == cache[i].is_used)
        {
            break;
        }
    }
    //如果没有空的缓存块,则驱逐最近最少使用的缓存块
    if(MAX_BLOCK_NUM == i)
    {
        int mincnt = cache[0].is_used;
        for(int j = 1; j < MAX_BLOCK_NUM; j ++)
        {
            if(cache[j].is_used < mincnt )
            {
                mincnt = cache[j].is_used;
                i = j;
            }
        }
    }
    strcpy(cache[i].uri, uri);
    strcpy(cache[i].obj, obj);
    cache[i].is_used = cal_cache_maxnum() + 1;

    //释放写信号量
    V(&rwlock.w);
    return;
}

//处理请求头
int handle_request_header(rio_t* rio, char* client_request, 
                          char* host, char* filename)
{
    char buf[MAXLINE];
    sprintf(client_request, "GET %s HTTP/1.0\r\n", filename);

    //标志是否有Host,User-Agent,Connection,Proxy-Connection
    bool is_host = false;
    bool is_user_agent = false;
    bool is_connection = false;
    bool is_proxy_connection = false;
    char* pos ;
    int n = Rio_readlineb(rio, buf, MAXLINE);
    //反复读取请求头
    while(0 != strcmp(buf, "\r\n") && 0 != n)
    {
        strcat(client_request, buf);
        if(NULL != (pos = strstr(buf, "Host:")))
        {
            is_host = true;
        }
        if(NULL != (pos = strstr(buf, "User-Agent:")))
        {
            is_user_agent = true;
        }
        if(NULL != (pos = strstr(buf, "Connection:")))
        {
            is_connection = true;
        }
        if(NULL != (pos = strstr(buf, "Proxy-Connection:")))
        {
            is_proxy_connection = true;
        }

        n = Rio_readlineb(rio, buf, MAXLINE);
    }
    //如果请求头为空,则返回
    if(0 == n)
    {
        return 0;
    }
    //根据实际情况添加请求头
    if(!is_host)
    {
        sprintf(buf, "Host: %s\r\n", host);
        strcat(client_request, buf);
    }

    if(!is_user_agent)
    {
        strcat(client_request, user_agent_hdr);
    }

    if(!is_connection)
    {
        sprintf(buf, "Connection: close\r\n");
        strcat(client_request, buf);
    }
    if(!is_proxy_connection)
    {
        sprintf(buf, "Proxy-Connection: close\r\n");
        strcat(client_request, buf);
    }
    //添加一个空行
    strcat(client_request, "\r\n");
    return 1;
}

//打印客户端错误
void print_client_error(int fd, char* cause, char* errnum,
                        char* shortmsg, char* longmsg)
{
    char buf[MAXLINE];

    //构建HTTP响应主体
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n\r\n");
    Rio_writen(fd, buf, strlen(buf));
 
    //打印HTTP响应的主体
    sprintf(buf, "<html><title>Tiny Error</title>");
    Rio_writen(fd,buf,strlen(buf));
    sprintf(buf, "<body bgcolor=""ffffff"">\r\n");
    Rio_writen(fd,buf,strlen(buf));
    sprintf(buf, "%s: %s\r\n", shortmsg, shortmsg);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<p>%s: %s\r\n", longmsg, cause);
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "<hr><em>The Tiny Web Server</em>\r\n");

}

//解析URI
void prase_uri(char* uri, char* hostname, char* port, char* filename)
{
    char* host_pos, * port_pos, * file_pos;

    //提取主机名,端口号和文件名
    host_pos = uri + 7;
    port_pos = strstr(host_pos, ":");
    file_pos = strstr(host_pos, "/");

    //提取主机名和端口号
    if(NULL == port_pos)
    {
        strcpy(port, "80");
        strncpy(hostname, host_pos, file_pos - host_pos);
        hostname[file_pos - host_pos] = '\0';
    }
    else
    {
        strncpy(hostname, host_pos, port_pos - host_pos);
        hostname[port_pos - host_pos] = '\0';
        strncpy(port, port_pos + 1, file_pos - port_pos - 1);
        port[file_pos - port_pos - 1] = '\0';
    }

    //提取文件名
    strcpy(filename, file_pos);
}

//处理请求
void handle_request(int fd)
{
    //分割请求行的参数
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];

    //请求头的主体
    char hostname[MAXLINE], port[MAXLINE], filename[MAXLINE];

    char client_request[MAXLINE];
    rio_t rio_server, rio_client;
    Rio_readinitb(&rio_client, fd);
    Rio_readlineb(&rio_client, buf, MAXLINE);

    //解析请求行
    sscanf(buf, "%s %s %s", method, uri, version);

    if(0 != strcmp(version, "HTTP/1.1"))
    {
        strncpy(version, "HTTP/1.0", 8);
        return;
    }
    if(0 != strcmp(method, "GET"))
    {
        print_client_error(fd, method, "501", 
                           "Not Implemented", 
                           "Proxy does not implement this method");
        return;
    }
    
    //读取缓存
    char* obj = read_cache(uri);
    //如果缓存中有数据,则直接返回
    if(NULL != obj)
    {
        Rio_writen(fd, obj, strlen(obj));
        free(obj);
        return;
    }
    //如果缓存中没有数据,则继续处理请求
    prase_uri(uri, hostname, port, filename);

    //处理后续的请求头
    if(0 == handle_request_header(&rio_client, 
                                   client_request, 
                                   hostname, filename))
    {
        return;
    }

    //与服务器建立连接
    int clientfd;
    if(0 > (clientfd = Open_clientfd(hostname, port)))
    {
        print_client_error(fd, hostname, "404", 
                           "Not found", 
                           "Proxy couldn't connect to the server");
        return;
    }

    Rio_readinitb(&rio_server, clientfd);
    size_t len = strlen(client_request);
    Rio_writen(clientfd, client_request, len);

    char response[MAXLINE];
    //缓存对象
    char obj_buf[MAX_OBJECT_SIZE];
    int num = 0;
    size_t obj_len = 0;
    //读取响应
    while(0 != (num = Rio_readlineb(&rio_server, response, MAXLINE)))
    {
        //将响应写回客户端
        Rio_writen(fd, response, num);
        //缓存响应
        if(num + obj_len < MAX_OBJECT_SIZE)
        {
            strcpy(obj_buf + obj_len, response);
            obj_len += num;
        }
        else
        {
            obj_len = MAX_OBJECT_SIZE + 1;
        }
    }
    //将缓存写入缓存
    write_cache(uri, obj_buf);
}

